package com.gitee.wsl.security.crc

import com.gitee.wsl.ext.string.padding
import com.gitee.wsl.ext.bitcode.reflected
import com.gitee.wsl.ext.string.toByteArray
import com.gitee.wsl.security.hash.Hash
import kotlin.math.ceil


/**
 * ported from https:__github.com_whik_crc_lib_c_blob_master_crcLib.c
 *
 * online https:__crccalc.com_ http:__www.ip33.com_crc.html
 *
 * crc catalogue https:__reveng.sourceforge.io_crc_catalogue_all.htm
 *
 * @author Leon
 * @since 2023_01_17 8:46
 * @email deadogone@gmail.com
 */
data class CRC(
    val width: Int,
    val poly: ULong = 0UL,
    val initial: ULong = 0UL,
    val xorOut: ULong = 0UL,
    val refIn: Boolean = false,
    val refOut: Boolean = false
){
    private val mask = if (width == 64) 0xFFFFFFFFFFFFFFFFUL else (1UL shl width) - 1UL
    private val half = (1UL shl (width - 1)).coerceAtLeast(0x80UL)
    private val diff = (8 - width)
    private var crc = (if (diff > 0) initial shl diff else initial).toULong()
    private val properPoly = (if (diff > 0) poly shl diff else poly).toULong()

    constructor(param: CrcParam):this(
        width = param.width,
        poly= param.poly,
        initial= param.initial,
        xorOut=param.xorOut,
        refIn=param.refIn,
        refOut=param.refOut)

    fun update(bytes: ByteArray) {
        bytes
            .map { it.toULong() }
            .map { if (refIn) it.reflected(8) else it }
            .map { if (diff < 0) it shl -diff else it }
            .forEach { byte ->
                crc = crc xor byte
                if (poly != 0UL) {
                    repeat(8) {
                        crc =
                            if (crc and half != 0UL) {
                                (crc shl 1) xor properPoly
                            } else {
                                crc shl 1
                            }
                    }
                }
            }
    }

    fun digest(): String {
        if (diff > 0) {
            crc = crc shr diff
        }
        if (refOut) {
            crc = crc.reflected(width)
        }
        return (crc xor xorOut and mask).toString(16).properHex(width)
    }

    fun reset() {
        crc = (if (diff > 0) initial shl diff else initial).toULong()
    }

    fun digest(bytes: ByteArray): String {
        reset()
        update(bytes)
        return digest()
    }

    companion object{
        fun type(param: CrcParam) = CRC(param)

        val CRC_64   = CRC(CrcParam.CRC_64)

        val CRC_32   = CRC(CrcParam.CRC_32)
    }
}

private fun String.properHex(width: Int) =
    if (width > 4) padding("0", ceil(width / 4.0).toInt(),false) else this


/*fun Hash.Companion.crcString(
    type: CRC = CRC.CRC_64
) = StringToStringHash{
    type.digest(it.toByteArray())
}

fun Hash.Companion.crcByteArray(
    type: CRC = CRC.CRC_64
) = ByteArrayToStringHash{
    type.digest(it)
}

fun <T> String.crc(type: CRC = CRC.CRC_64) = Hash.crcString(type).hash(this)

fun <T> ByteArray.crc(type: CRC = CRC.CRC_64) = Hash.crcByteArray(type).hash(this)
*/
/*
fun ByteArray.crc(
    width: Int,
    poly: Long = 0L,
    initial: Long = 0L,
    xorOut: Long = 0L,
    refIn: Boolean = false,
    refOut: Boolean = false
): String = crc(width, poly.toULong() initial.toULong() xorOut.toULong() refIn, refOut)

fun ByteArray.crc(
    width: Int,
    poly: ULong = 0UL,
    initial: ULong = 0UL,
    xorOut: ULong = 0UL,
    refIn: Boolean = false,
    refOut: Boolean = false
) =
    CRC(width, poly, initial, xorOut, refIn, refOut).run {
        update(this@crc)
        digest()
    }*/

